#include <framework/framework_i.h>

/* For the naive mapping strategy */
#define PADDR(kva) ((physaddr_t)(kva) - KBASE)
#define KADDR(pa)  (void *)((pa) + KBASE)

/* I/O APIC Code from xv6 */

#define REG_ID     0x00  // Register index: ID
#define REG_VER    0x01  // Register index: version
#define REG_TABLE  0x10  // Redirection table base

// The redirection table starts at REG_TABLE and uses
// two registers to configure each interrupt.  
// The first (low) register in a pair contains configuration bits.
// The second (high) register contains a bitmask telling which
// CPUs can serve that interrupt.
#define INT_DISABLED   0x00010000  // Interrupt disabled
#define INT_LEVEL      0x00008000  // Level-triggered (vs edge-)
#define INT_ACTIVELOW  0x00002000  // Active low (vs high)
#define INT_LOGICAL    0x00000800  // Destination is CPU id (vs APIC ID)

// IO APIC MMIO structure: write reg, then read or write data.
struct ioapic_mmio_s
{
	 uint32_t reg;
	 uint32_t __pad0[3];
	 uint32_t data;
	 uint32_t __pad1[3];
	 uint32_t irq_pin_ass;
	 uint32_t __pad2[7];
	 uint32_t eoi;
};
typedef volatile struct ioapic_mmio_s ioapic_mmio_t;

static uint32_t
ioapic_read(ioapic_mmio_t *ioapic, int reg)
{
	 ioapic->reg = reg;
	 return ioapic->data;
}

static void
ioapic_write(ioapic_mmio_t *ioapic, int reg, uint32_t data)
{
	 ioapic->reg = reg;
	 ioapic->data = data;
}

volatile struct ioapic_s ioapics[LAPIC_COUNT];

int
ioapic_init(void)
{
	 if (ekf_sysconf.ioapic_count == 0 ||
		 !ekf_sysconf.use_ioapic)
		  return 0;
	 
	 int i, id, maxintr;
	 int ioapic_id;
	 ioapic_mmio_t *ioapic;
	 
	 for (ioapic_id = 0; ioapic_id != ekf_sysconf.ioapic_count; ++ ioapic_id)
	 {
		  kprintf("IOAPIC %d phys %08x\n", ioapic_id, ioapics[ioapic_id].phys);
		  /* PG is not enabled here, so naive works */
		  ioapic = (ioapic_mmio_t *) KADDR(ioapics[ioapic_id].phys);

		  int v = ioapic_read(ioapic, REG_VER);
		  maxintr = (v >> 16) & 0xFF;
		  kprintf("VERSION: %02x\n", v & 0xFF);
		  kprintf("MAXINTR: %d\n", maxintr);
		  if ((v & 0xFF) >= 0x20)
			   ekf_sysconf.use_ioapic_eoi = 1;
		  else ekf_sysconf.use_ioapic_eoi = 0;
		  id = (ioapic_read(ioapic, REG_ID) >> 24) & 0xF;
		  if (id != ioapics[ioapic_id].apic_id)
		  {
			   kprintf("ioapic_init: id %08x isn't equal to ioapic_id %08x ; Fixing\n",
					   id, ioapics[ioapic_id].apic_id);
			   // ioapic_write(ioapic, REG_ID, ioapics[ioapic_id].apic_id << 24);
		  }

#if 1
		  // Mark all interrupts edge-triggered, active high, disabled,
		  // and not routed to any CPUs.
		  for(i = 0; i <= maxintr; i++)
		  {
			   ioapic_write(ioapic, REG_TABLE + 2 * i, INT_DISABLED | (IRQ_OFFSET + i));
			   ioapic_write(ioapic, REG_TABLE + 2 * i + 1, 0);
		  }
#else
		  // Redirect all interrupt to cpu 0
		  for(i = 0; i <= maxintr; i++)
		  {
			   /* Omit the 2nd IRQ controller? */
			   // if (i == IRQ_SCTL) continue;
			   ioapic_write(ioapic, REG_TABLE + 2 * i, (IRQ_OFFSET + i));
			   ioapic_write(ioapic, REG_TABLE + 2 * i + 1, 0);
		  }
#endif
	 }

	 // IMCR:
	 // Bochs doesn't support IMCR, so this doesn't run on Bochs.
	 // But it would on real hardware.
	 outb(0x22, 0x70);   // Select IMCR
	 outb(0x23, inb(0x23) | 1);  // Mask external interrupts.

	 return 0;
}

// XXX THIS ROUTINE IS BUGGY
void
ioapic_send_eoi(int irq)
{
	 kprintf("ioapic eoi %d\n", irq);
	 ioapic_mmio_t *ioapic;
	 BEGIN_TPAGE(0xFEC00000, ioapic);
	 ioapic->eoi = irq & 0xFF;
	 END_TPAGE;
}

void
ioapic_enable(void *_ioapic, int irq, int cpunum)
{
	 ioapic_mmio_t *ioapic = (ioapic_mmio_t *)_ioapic;
	 // Mark interrupt edge-triggered, active high,
	 // enabled, and routed to the given cpunum,
	 // which happens to be that cpu's APIC ID.
	 ioapic_write(ioapic, REG_TABLE + 2 * irq, IRQ_OFFSET + irq);
	 ioapic_write(ioapic, REG_TABLE + 2 * irq + 1, cpunum << 24);
}

void
ioapic_disable(void *_ioapic, int irq)
{
	 ioapic_mmio_t *ioapic = (ioapic_mmio_t *)_ioapic;
	 // Mark interrupt edge-triggered, active high,
	 // enabled, and routed to the given cpunum,
	 // which happens to be that cpu's APIC ID.
	 ioapic_write(ioapic, REG_TABLE + 2 * irq, INT_DISABLED | (IRQ_OFFSET + irq));
	 ioapic_write(ioapic, REG_TABLE + 2 * irq + 1, 0);
}
